import {
  Component,
  OnInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  OnDestroy,
  ViewContainerRef,
  ViewChild,
  TemplateRef,
  ElementRef,
  ChangeDetectorRef,
  EventEmitter,
  Output,
  TrackByFunction,
  Renderer2,
  OnChanges,
  QueryList,
  ViewChildren,
} from '@angular/core';
import { AnimationEvent } from '@angular/animations';
import { TemplatePortal } from '@angular/cdk/portal';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { takeUntil, distinctUntilChanged, debounceTime } from 'rxjs/operators';
import { Subject, merge, BehaviorSubject } from 'rxjs';
import { OverlayService, MenuService, InputBoolean, InputNumber, LvSizeLSD, showSubItem } from '../core/api';
import { ExtendOverlayConfig } from '../core/overlay/api';
import { SelectNode } from './interface';
import { SelectService } from './select.service';
import { OptionComponent } from './option.component';

// NEED_DO: 支持 ng-container *ngTemplateOutlet 递归写法
@Component({
  selector: 'lv-subselect',
  templateUrl: './sub-select.component.html',
  providers: [MenuService],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [showSubItem],
  host: {
    class: 'lv-subselect',
    '(mouseenter)': '_mouseenter($event)',
    '(mouseleave)': '_mouseleave($event)',
  },
})
export class SubSelectComponent implements OnInit, OnDestroy, OnChanges {
  @Input() lvIndex: number;
  // 输入输出属性
  @Input() lvData: SelectNode;
  @Input() lvHighlightContent: string;
  @Input() lvSize: LvSizeLSD = 'default';
  @Input() lvTrackBy: TrackByFunction<any>; // 内容列表trackBy
  @Input() @InputNumber() lvVirtualItemSize = 34; // 每行高度
  // == virtual scroll
  @Input() @InputBoolean() lvVirtualScroll = false; // 是否使用虚拟滚动（大数据场景使用）

  @Input() @InputNumber() lvVirtualMaxBuffer = 200; // 渲染新项时最大缓冲高度
  @Input() @InputNumber() lvVirtualMinBuffer = 100; // 视图之外渲染的最小缓冲区数，若缓存区数量低于此数量将渲染新项
  // @Output() lvSelectChange = new EventEmitter<SubSelectComponent>(); // 点击事件回调
  @Output() lvSelectedChange = new EventEmitter<any>(); // 选中状态变更事件回调, 返回选中状态和活动节点
  @Input() lastVirtualScrollHoveredIndex: number | undefined; // 用于虚拟滚动scrollEnd导致的数据重新渲染的情况，只支持一层数据的情况
  @Input() lvScroll: { x?: string | null; y?: string | null }; // 下拉选项水平垂直高度，超过滚动 目前只有y

  @Input() lvContentTemplate: TemplateRef<{ $implicit: any }>; // 自定义内容模板

  @ViewChildren('subSelect') subSelect: QueryList<SubSelectComponent>;
  @ViewChild(OptionComponent, { static: false }) _innerOption: OptionComponent; // 内部展示的option
  @ViewChild('selectionPanelTpl', { static: false }) _selectionPanelTpl: TemplateRef<any>;
  @ViewChild('scrollComponent', { static: false }) _scrollViewport: CdkVirtualScrollViewport;
  overlayRef;
  scrollY: string;
  clear: boolean = false; // 标识，单选时判断是不是外部触发清除

  /**
   * 选中场景：
   * 1. 子级全部被选中（规范未提及，暂时关闭此样式）
   * 2. 直接选中本身
   */
  active: boolean = false; // 子级被选中
  animationState = 'void';
  // 外部可访问变量
  multiple: boolean = false; // 是否为多选模式

  highlight: boolean = false; // 子级展开

  parent: SubSelectComponent;
  overlayOpen: boolean = false;
  toggleSelected$ = new Subject<boolean>(); // 选中流
  // 内部变量
  private _destroy$ = new Subject<void>();

  private _refresh$ = new Subject<void>(); // 更新流
  private _openState$ = new Subject<boolean>();
  private _toggleOptionList$ = new BehaviorSubject<any>(null);
  constructor(
    private _menuService: MenuService,
    private viewContainerRef: ViewContainerRef,
    private overlayService: OverlayService,
    // NEED_DO: https://github.com/angular/angular/issues/14842
    private selectService: SelectService,
    private overlay: Overlay,
    public elementRef: ElementRef,
    private _cdr: ChangeDetectorRef,
    private renderer: Renderer2,
  ) {
    this.multiple = this.selectService.isMultipleMode();
  }

  get panelClass(): any {
    return this.selectService.getPanelClass();
  }

  setRefresh(): void {
    this._refresh$.next();
  }

  _emitSelected(source): void {
    this.lvSelectedChange.emit(source);
  }

  _mouseenter(): void {
    this._toggleMouseStatus(true);
  }

  _mouseleave(): void {
    this._toggleMouseStatus(false);
  }

  ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();
  }

  // 关闭下拉
  clearOverlayRef(): void {
    if (this._existOverlayRef()) {
      this.overlayRef.close();
    }
  }

  _existOverlayRef(): any {
    return this.overlayRef?.overlayRef.hasAttached();
  }

  ngOnChanges(): void {
    this.scrollY = this.selectService.getScrollY(this.lvScroll, this.lvSize);
  }

  /**
   * 点击有一个node， 单选时关闭overlay
   */
  optionSelectChange(value: boolean): void {
    this.itemSeletedChange({ value, node: this.lvData });
  }

  getFirstActiveIndex(): any {
    return this.selectService.getActiveNodeFirstIndex(this.lvData.children || []);
  }

  _subscribeMenuOpenChange(): void {
    this._menuService.menuOpen$.pipe(takeUntil(this._destroy$)).subscribe((): void => {});
  }

  _toggleMouseStatus(status: boolean): void {
    if (this.lvData.isLeaf) {
      return;
    }
    this._menuService.setMouseEnterState(status);
  }

  onAnimationDone(event: AnimationEvent): void {
    if (event.fromState === 'show' && event.toState === 'hide') {
      this._openState$.next(false);
    }
  }

  _getActive(source: SelectNode): boolean {
    if (!source.children || !source.children.length) {
      return undefined;
    }
    return source.children.some((item): boolean => item.selected);
  }

  itemSeletedChange(source: any): void {
    if (!this.multiple && !this.lvData.isLeaf) {
      this._mouseleave();
      this._toggleOptionList$.next(source);
      return;
    }
    this._emitSelected(source);
  }

  ngOnInit(): void {
    // 订阅menuOpen$消息
    this._subscribeMenuOpenChange();

    // 订阅open$（overlay开关）消息
    this._subscribeOpenChange();

    // 订阅更新
    merge(this.selectService.getRefresh(), this._refresh$)
      .pipe(debounceTime(1), takeUntil(this._destroy$))
      .subscribe((): void => this._cdr.markForCheck());
  }

  /**
   * overlay 开启 关闭
   */
  _subscribeOpenChange(): void {
    this._menuService.open$.pipe(distinctUntilChanged(), takeUntil(this._destroy$)).subscribe((open: boolean): void => {
      this.overlayOpen = open;
      open ? this._openPanelOverlay() : this.clearOverlayRef();

      // subselect active
      this.highlight = open;
      this.setRefresh();
    });
  }

  // 打开下拉panel overlay
  _openPanelOverlay(): void {
    if (!this._existOverlayRef()) {
      const config = this.getOverlayConfig();
      const extendConfig = this.getExtendConfig();

      // 处理没点按钮 下拉消失选中状态不对应问题
      const portal = new TemplatePortal(this._selectionPanelTpl, this.viewContainerRef);
      this.overlayRef = this.overlayService.open(portal, config, extendConfig);
    }
  }

  setPanelHighlightPosition(dom: any): void {
    const contentDom = dom;
    if (!contentDom) {
      return;
    }
    const index = this.getFirstActiveIndex();
    const optionsHeight = contentDom.offsetHeight;
    const itemHeight = this.lvVirtualItemSize;
    const maxHeight = parseInt(this.scrollY, 0);
    if (index < 0 || optionsHeight < maxHeight) {
      return;
    }
    if (this.lvVirtualScroll) {
      // NEED_DO: 滚动到第一个选中项 setTimeout(() => this._scrollViewport.scrollToIndex(index), 0);
      return;
    }
    contentDom.scrollTop = (index + 1) * itemHeight - optionsHeight / 2;
  }

  getOverlayConfig(): OverlayConfig {
    const dom = this.elementRef.nativeElement;
    const originClass = 'lv-select-panel-origin';
    const originWidth = dom.querySelector(`.${originClass}`).offsetWidth;
    const parentPanelWidth = dom.parentNode.parentNode.offsetWidth;
    return new OverlayConfig({
      hasBackdrop: false,
      scrollStrategy: this.overlay.scrollStrategies.block(),
      positionStrategy: this.overlay
        .position()
        .flexibleConnectedTo(this.elementRef)
        .withPositions([
          {
            originX: 'end',
            originY: 'top',
            overlayX: 'start',
            overlayY: 'top',
            offsetX: parentPanelWidth === originWidth ? 0 : parentPanelWidth - originWidth,
          },
          {
            originX: 'start',
            originY: 'top',
            overlayX: 'end',
            overlayY: 'top',
            offsetX: -16, // NEED_DO: 向左展开面板动画后续添加，暂以添加偏移方式解决
          },
        ]),
    });
  }

  getExtendConfig(): ExtendOverlayConfig {
    return {
      clickToCloseStrategy: false,
      scrollToCloseStrategy: false,
      afterOpen: (overlayRef: any, res: any): void => {
        // 设置z-index
        this.renderer.addClass(overlayRef.overlayRef.hostElement, 'lv-select-panel-z-index');
        // 定位到第一个选中位置
        this.setPanelHighlightPosition(res.rootNodes[0].querySelector('.lv-select-panel-content'));

        this.animationState = 'show';
      },
      beforeClose: (overlayRef): Promise<void> =>
        new Promise<void>((resolve): void => {
          this._openState$.next(true);
          this.animationState = 'hide';
          // Add a mask after the animation started to stop mouse event
          overlayRef.overlayRef.overlayElement.style.position = 'relative';
          const div = document.createElement('div');
          div.setAttribute(
            'style',
            'background: rgba(0, 0, 0, 0); left: 0; top: 0; bottom: 0; right:0; position: absolute;',
          );
          overlayRef.overlayRef.overlayElement.append(div);

          const closesubscribe = this._openState$.subscribe((open): void => {
            if (!open) {
              closesubscribe.unsubscribe();
              resolve();
            }
          });
        }),
      afterClose: (): void => {
        const source = this._toggleOptionList$.value;
        if (!source) {
          return;
        }
        this._emitSelected(source);
        this._toggleOptionList$.next(null);
      },
    };
  }
}
